// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#include "avtTrackball.h"
#include <algorithm>

//
// Use a Witch of Agnesi for a trackball.
//
// This size should really be based on the distance from the center
// of rotation to the point on the object underneath the mouse.  That
// point would then track the mouse as closely as possible.
//
#define AGNESI_RADIUS   0.8     // Z value at x = y = 0.0
#define COMPRESSION     3.5     // Multipliers for x and y
#define AR3 (AGNESI_RADIUS*AGNESI_RADIUS*AGNESI_RADIUS)

// ****************************************************************************
//  Constructor:  avtTrackball::avtTrackball
//
//  Purpose:
//    Initialize the trackball.
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
avtTrackball::avtTrackball()
{
    constrain = false;
    center = avtVector(0,0);
}

// ****************************************************************************
//  Method:  avtTrackball::SetCenter
//
//  Purpose:
//    Sets the center of rotation in unit screen space.
//
//  Arguments:
//    c          the center
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
void
avtTrackball::SetCenter(const avtVector &c)
{
    center = c;
}

// ****************************************************************************
//  Method:  avtTrackball::SetCamera
//
//  Purpose:
//    Set a camera to use for transforming the rotation into world space.
//
//  Arguments:
//    C          the current camera matrix
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
void
avtTrackball::SetCamera(const avtMatrix &C)
{
    inverse_camera = C;
    inverse_camera.Inverse();
}

// ****************************************************************************
//  Method:  avtTrackball::ClearCamera
//
//  Purpose:
//    Disable transforming from camera to world space.
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
void
avtTrackball::ClearCamera()
{
    inverse_camera.MakeIdentity();
}

// ****************************************************************************
//  Method:  avtTrackball::SetConstrainAxis
//
//  Purpose:
//    Set an axis to constrain to.
//
//  Arguments:
//    v          the axis
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
void
avtTrackball::SetConstrainAxis(const avtVector &v)
{
    constrainAxis = v.normalized();
    constrain = true;
}

// ****************************************************************************
//  Method:  avtTrackball::ClearConstrainAxis
//
//  Purpose:
//    Disable constraining to an axis.
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
void
avtTrackball::ClearConstrainAxis()
{
    constrain = false;
}

// ****************************************************************************
//  Method:  avtTrackball::GetQuaternion
//
//  Purpose:
//    Return the calculated rotation quaternion.
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
const avtQuaternion &
avtTrackball::GetQuaternion() const
{
    return q;
}

// ****************************************************************************
//  Method:  avtTrackball::GetMatrix
//
//  Purpose:
//    Return the calculated rotation matrix.
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
const avtMatrix &
avtTrackball::GetMatrix() const
{
    return M;
}

// ****************************************************************************
//  Method:  avtTrackball::PerformRotation
//
//  Purpose:
//    Create a trackball rotation using two points in unit screen splace
//
//  Arguments:
//    s1         the previous coordinate
//    s2         the current coordinate
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
void
avtTrackball::PerformRotation(const avtVector &s1, const avtVector &s2)
{
    // Get the rotation axis from the mouse coordinates
    avtVector p1 = Project(s1-center);
    avtVector p2 = Project(s2-center);
    avtVector rot_axis = (p1 % p2).normalized();

    // Put the rotation axis in world space if needed
    rot_axis = inverse_camera ^ rot_axis;

    // Constrain to a given axis, if needed
    double scale = 1.0;
    if (constrain)
    {
        scale = rot_axis * constrainAxis;
        rot_axis = constrainAxis;
    }

    // Figure how much to rotate around that axis.
    double d = (p2 - p1).norm();
    d = std::max(0.,std::min(1.,d));
    double phi = 2.0 * asin(d / (2.0 * AGNESI_RADIUS));

    // Create the quaternion and the rotation matrix
    q = avtQuaternion(rot_axis, phi * scale);
    M = q.CreateRotationMatrix();
}

// ****************************************************************************
//  Method:  avtTrackball::Project
//
//  Purpose:
//    Project a point in unit screen space onto the trackball.
//
//  Arguments:
//    p          the 2d point in unit screen space
//
//  Programmer:  Jeremy Meredith
//  Creation:    October  9, 2001
//
// ****************************************************************************
avtVector
avtTrackball::Project(const avtVector &s)
{
    double z = double(AR3) / ((s.x*s.x + s.y*s.y) * COMPRESSION + AR3);
    return avtVector(s.x, s.y, z);
}

